/*
 * <summary></summary>
 * <author>He Han</author>
 * <email>hankcs.cn@gmail.com</email>
 * <create-date>2014/8/22 14:17</create-date>
 *
 * <copyright file="BM25.java" company="上海林原信息科技有限公司">
 * Copyright (c) 2003-2014, 上海林原信息科技有限公司. All Right Reserved, http://www.linrunsoft.com/
 * This source is subject to the LinrunSpace License. Please contact 上海林原信息科技有限公司 to get more information.
 * </copyright>
 */
using C5;
using System;

namespace com.hankcs.hanlp.summary
{


    /**
     * 搜索相关性评分算法
     * @author hankcs
     */
    public class BM25
    {
        /**
         * 文档句子的个数
         */
        int D;

        /**
         * 文档句子的平均长度
         */
        double avgdl;

        /**
         * 拆分为[句子[单词]]形式的文档
         */
        IList<IList<string>> docs;

        /**
         * 文档中每个句子中的每个词与词频
         */
        TreeDictionary<string, int>[] f;

        /**
         * 文档中全部词语与出现在几个句子中
         */
        TreeDictionary<string, int> df;

        /**
         * IDF
         */
        TreeDictionary<string, double> idf;

        /**
         * 调节因子
         */
        const float k1 = 1.5f;

        /**
         * 调节因子
         */
        const float b = 0.75f;

        public BM25(IList<IList<String>> docs)
        {
            this.docs = docs;
            D = docs.Count;
            foreach (var sentence in docs)
            {
                avgdl += sentence.Count;
            }
            avgdl /= D;
            f = new TreeDictionary<string, int>[D];
            df = new TreeDictionary<string, int>();
            idf = new TreeDictionary<string, double>();
            init();
        }

        /**
         * 在构造时初始化自己的所有参数
         */
        private void init()
        {
            int index = 0;
            foreach (var sentence in docs)
            {
                var tf = new TreeDictionary<string, int>();
                foreach (string word in sentence)
                {
                    int freq = tf.Contains(word) ? tf[word] : 0;
                    freq += 1;
                    tf[word] = freq;
                }
                f[index] = tf;
                foreach (var entry in tf.Keys)
                {
                    string word = entry;
                    int freq = df.Contains(word) ? df[word] : 0;
                    freq += 1;
                    df[word] = freq;
                }
                ++index;
            }
            foreach (var entry in df.Keys)
            {
                string word = entry;
                int freq = df[word];
                idf[word] = Math.Log(D - freq + 0.5) - Math.Log(freq + 0.5);
            }
        }

        public double sim(IList<string> sentence, int index)
        {
            double score = 0;
            foreach (var word in sentence)
            {
                if (!f[index].Contains(word)) continue;
                int d = docs[index].Count;
                int wf = f[index][word];
                score += (idf[word] * wf * (k1 + 1)
                        / (wf + k1 * (1 - b + b * d
                                                    / avgdl)));
            }

            return score;
        }

        public double[] simAll(IList<string> sentence)
        {
            double[] scores = new double[D];
            for (int i = 0; i < D; ++i)
            {
                scores[i] = sim(sentence, i);
            }
            return scores;
        }
    }
}